package se.devoteam.nexus.ldap;

import java.util.HashSet;
import java.util.Set;

import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.LdapContext;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.LogEnabled;
import org.codehaus.plexus.logging.Logger;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Initializable;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
import org.jsecurity.authc.AuthenticationInfo;
import org.jsecurity.authc.AuthenticationToken;
import org.jsecurity.authz.AuthorizationException;
import org.jsecurity.authz.AuthorizationInfo;
import org.jsecurity.authz.SimpleAuthorizationInfo;
import org.jsecurity.realm.Realm;
import org.jsecurity.realm.ldap.AbstractLdapRealm;
import org.jsecurity.realm.ldap.LdapContextFactory;
import org.jsecurity.realm.ldap.LdapUtils;
import org.jsecurity.subject.PrincipalCollection;

/**
 * LDAP authorizing realm for Sonatype Nexus.
 * 
 * @author Joel Bibrant
 * @author Anders Hammar
 */
//@Component(role = Realm.class, hint = "LdapAuthorizingRealm")
public class LdapAuthorizingRealm extends AbstractLdapRealm implements Initializable, Disposable, LogEnabled {

	private Logger logger;
	
    @Requirement
	private LdapConfigurationManager configuration;
    
	@Requirement
	private LdapContextFactory ctxFactory;

	@Override
	public String getName() {
		return LdapAuthorizingRealm.class.getName();
	}

	public void initialize() throws InitializationException {
		getLogger().info("Initializing LdapAuthorizingRealm");

		// Use our LdapContextFactory implementation
		super.setLdapContextFactory(this.ctxFactory);

		super.init();
	}

	public void dispose() {
		getLogger().info("Disposing LdapAuthorizingRealm");
	}
	
	protected Logger getLogger() {
		return logger;
	}

	public void enableLogging(Logger logger) {
		this.logger = logger;
	}
	
	/**
	 * No authentication taking place (always returns <code>null</code>).
	 * 
	 * @return <code>null</code>
	 */
	@Override
	protected AuthenticationInfo queryForAuthenticationInfo(AuthenticationToken arg0, LdapContextFactory arg1)
			throws NamingException {
		return null;
	}

	@Override
	protected AuthorizationInfo queryForAuthorizationInfo(PrincipalCollection principals, LdapContextFactory ldapContextFactory)
			throws NamingException {
        if (principals == null) {
			throw new AuthorizationException("Cannot authorize with no principals.");
		}

		String username = (String) principals.iterator().next();
		if (username == null) {
			getLogger().debug("Username is null - failing ldap authorization");
			return null;
		}
			
		LdapContext ctx = null;
		try {
			if (getLogger().isDebugEnabled()) {
				getLogger().debug("Authorizing user '" + username + "' through LDAP");
			}
			
			// Get the base for the search
			String searchBase = this.configuration.getSearchBase();

			// Create the LDAP search filter
			String searchFilterExpr = this.configuration.getSearchFilter();
			Object[] searchFilerArgs = {username};
			if (getLogger().isDebugEnabled()) {
				getLogger().debug("LDAP authorization search filter: " + searchFilterExpr);
			}

			// Create the search controls
			SearchControls searchCtls = new SearchControls();
		
			// Specify the search scope
			searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
	
			// Specify the attributes to return
			final String groupNameAttrId = this.configuration.getGroupNameAttributeId();
			String searchAttributes[]= {groupNameAttrId};
			searchCtls.setReturningAttributes(searchAttributes);
			
			// Search for objects using the search base, the search filter, and the search controls
			ctx = ldapContextFactory.getSystemLdapContext();
			NamingEnumeration<SearchResult> answer = ctx.search(searchBase, searchFilterExpr, searchFilerArgs, searchCtls);

			Set<String> roles = new HashSet<String>();
			while(answer.hasMore()) {
				Attributes attributes = answer.next().getAttributes();
				Attribute attribute = attributes.get(groupNameAttrId);
				roles.add((String) attribute.get());
			}
	
			return new SimpleAuthorizationInfo(roles);
		} finally {
			LdapUtils.closeContext(ctx);
		}
	}
}
